bulltrackers-module 1.0.732 → 1.0.733

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 (52) hide show
  1. package/functions/orchestrator/index.js +19 -17
  2. package/index.js +8 -29
  3. package/package.json +1 -1
  4. package/functions/computation-system/WorkflowOrchestrator.js +0 -213
  5. package/functions/computation-system/config/monitoring_config.js +0 -31
  6. package/functions/computation-system/config/validation_overrides.js +0 -10
  7. package/functions/computation-system/context/ContextFactory.js +0 -143
  8. package/functions/computation-system/context/ManifestBuilder.js +0 -379
  9. package/functions/computation-system/data/AvailabilityChecker.js +0 -236
  10. package/functions/computation-system/data/CachedDataLoader.js +0 -325
  11. package/functions/computation-system/data/DependencyFetcher.js +0 -455
  12. package/functions/computation-system/executors/MetaExecutor.js +0 -279
  13. package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
  14. package/functions/computation-system/executors/StandardExecutor.js +0 -465
  15. package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
  16. package/functions/computation-system/helpers/computation_worker.js +0 -375
  17. package/functions/computation-system/helpers/monitor.js +0 -64
  18. package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
  19. package/functions/computation-system/layers/extractors.js +0 -1097
  20. package/functions/computation-system/layers/index.js +0 -40
  21. package/functions/computation-system/layers/mathematics.js +0 -522
  22. package/functions/computation-system/layers/profiling.js +0 -537
  23. package/functions/computation-system/layers/validators.js +0 -170
  24. package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
  25. package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
  26. package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
  27. package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
  28. package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
  29. package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
  30. package/functions/computation-system/logger/logger.js +0 -297
  31. package/functions/computation-system/persistence/ContractValidator.js +0 -81
  32. package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
  33. package/functions/computation-system/persistence/ResultCommitter.js +0 -283
  34. package/functions/computation-system/persistence/ResultsValidator.js +0 -130
  35. package/functions/computation-system/persistence/RunRecorder.js +0 -142
  36. package/functions/computation-system/persistence/StatusRepository.js +0 -52
  37. package/functions/computation-system/reporter_epoch.js +0 -6
  38. package/functions/computation-system/scripts/UpdateContracts.js +0 -128
  39. package/functions/computation-system/services/SnapshotService.js +0 -148
  40. package/functions/computation-system/simulation/Fabricator.js +0 -285
  41. package/functions/computation-system/simulation/SeededRandom.js +0 -41
  42. package/functions/computation-system/simulation/SimRunner.js +0 -51
  43. package/functions/computation-system/system_epoch.js +0 -2
  44. package/functions/computation-system/tools/BuildReporter.js +0 -531
  45. package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
  46. package/functions/computation-system/tools/DeploymentValidator.js +0 -536
  47. package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
  48. package/functions/computation-system/topology/HashManager.js +0 -55
  49. package/functions/computation-system/topology/ManifestLoader.js +0 -47
  50. package/functions/computation-system/utils/data_loader.js +0 -675
  51. package/functions/computation-system/utils/schema_capture.js +0 -121
  52. package/functions/computation-system/utils/utils.js +0 -188
@@ -1,478 +0,0 @@
1
- /**
2
- * FILENAME: computation-system/data/DependencyFetcher.js
3
- * @fileoverview Fetches dependencies for computations.
4
- * UPDATED: Added 'fetchExistingResults' bridge for WorkflowOrchestrator compatibility.
5
- * UPDATED: Uses 'manifestLookup' to resolve the correct category (Core vs Non-Core).
6
- * UPDATED: Supports automatic reassembly of sharded results (_shards subcollection).
7
- * UPDATED: Supports decompression of zipped results.
8
- */
9
- const { normalizeName } = require('../utils/utils');
10
- const zlib = require('zlib');
11
-
12
- /**
13
- * Checks if data is effectively empty (no usable content).
14
- * @param {any} data - The data to check
15
- * @returns {boolean} True if data is empty/null/undefined or contains no meaningful content
16
- */
17
- function isDataEmpty(data) {
18
- if (!data || data === null || data === undefined) return true;
19
-
20
- // Check if it's an object with only metadata fields
21
- if (typeof data === 'object' && !Array.isArray(data)) {
22
- const keys = Object.keys(data);
23
- // If only metadata/internal fields, consider it empty
24
- const metadataFields = ['_completed', '_compressed', '_sharded', '_shardCount', '_isPageMode', '_pageCount', '_lastUpdated', '_expireAt'];
25
- const hasOnlyMetadata = keys.length > 0 && keys.every(k => metadataFields.includes(k) || k.startsWith('_'));
26
-
27
- if (hasOnlyMetadata) return true;
28
-
29
- // If object has no keys (after filtering metadata), it's empty
30
- const dataKeys = keys.filter(k => !k.startsWith('_'));
31
- if (dataKeys.length === 0) return true;
32
- }
33
-
34
- // Check if it's an empty array
35
- if (Array.isArray(data) && data.length === 0) return true;
36
-
37
- return false;
38
- }
39
-
40
- /**
41
- * BRIDGE FUNCTION: Matches WorkflowOrchestrator signature.
42
- * Adapts (dateStr, calcs, manifest, ...) -> (dateObj, calcs, ..., manifestLookup).
43
- * This fixes the 'fetchExistingResults is not a function' TypeError.
44
- */
45
- async function fetchExistingResults(dateStr, calcs, fullManifest, config, deps, isHistoricalContext) {
46
- const { logger } = deps;
47
-
48
- // DEBUG: Log entry
49
- logger.log('INFO', `[DependencyFetcher] 📥 fetchExistingResults called for date: ${dateStr}, calcs: ${calcs.length}, isHistorical: ${isHistoricalContext}`);
50
- logger.log('INFO', `[DependencyFetcher] 📥 Calcs being processed: ${calcs.map(c => {
51
- const name = c.name || c.constructor?.name || 'unknown';
52
- const hasClass = !!c.class;
53
- const classType = c.class ? (typeof c.class === 'function' ? 'function' : typeof c.class) : 'none';
54
- return `${name} (has class: ${hasClass}, class type: ${classType})`;
55
- }).join(', ')}`);
56
-
57
- // 1. Build Manifest Lookup (Name -> Category)
58
- const manifestLookup = {};
59
- if (Array.isArray(fullManifest)) {
60
- fullManifest.forEach(c => {
61
- manifestLookup[normalizeName(c.name)] = c.category || 'analytics';
62
- });
63
- logger.log('INFO', `[DependencyFetcher] 📥 Built manifest lookup with ${Object.keys(manifestLookup).length} entries`);
64
- } else {
65
- logger.log('WARN', `[DependencyFetcher] ⚠️ fullManifest is not an array: ${typeof fullManifest}`);
66
- }
67
-
68
- // 2. Convert Date String to Date Object
69
- // We append T00:00:00Z to ensure it parses as UTC date if only YYYY-MM-DD is provided.
70
- const dateObj = new Date(dateStr + (dateStr.includes('T') ? '' : 'T00:00:00Z'));
71
-
72
- // 3. Delegate to fetchDependencies
73
- // CRITICAL: For historical context (yesterday's data), allow missing dependencies
74
- // Historical lookbacks are optional - gaps in historical data are permissible
75
- const result = await fetchDependencies(dateObj, calcs, config, deps, manifestLookup, isHistoricalContext);
76
-
77
- // DEBUG: Log result
78
- const resultKeys = Object.keys(result);
79
- logger.log('INFO', `[DependencyFetcher] 📤 fetchExistingResults returning ${resultKeys.length} dependencies: ${resultKeys.length > 0 ? resultKeys.join(', ') : 'NONE'}`);
80
-
81
- return result;
82
- }
83
-
84
- /**
85
- * Fetches dependencies for a specific date (Standard pass).
86
- * @param {Date} date - The target date.
87
- * @param {Array} calcs - The computations requiring dependencies.
88
- * @param {Object} config - System config.
89
- * @param {Object} deps - System dependencies (db, logger).
90
- * @param {Object} manifestLookup - Map of { [calcName]: categoryString }.
91
- * @param {boolean} allowMissing - If true, missing/empty dependencies are allowed (for historical/lookback scenarios).
92
- */
93
- async function fetchDependencies(date, calcs, config, deps, manifestLookup = {}, allowMissing = false) {
94
- const { db, logger } = deps;
95
- const dStr = date.toISOString().slice(0, 10);
96
-
97
- // DEBUG: Log entry
98
- logger.log('INFO', `[DependencyFetcher] 🔍 fetchDependencies called with ${calcs.length} calc(s): ${calcs.map(c => c.name || c.constructor?.name || 'unknown').join(', ')}`);
99
-
100
- // 1. Identify unique dependencies needed
101
- // CHANGED: Use a Map to track { normalizedName: originalName }
102
- const needed = new Map();
103
-
104
- calcs.forEach(c => {
105
- const calcName = c.name || c.constructor?.name || 'unknown';
106
-
107
- // DEBUG: Log what we're checking
108
- logger.log('INFO', `[DependencyFetcher] 🔍 Processing calc: ${calcName}`);
109
- logger.log('INFO', `[DependencyFetcher] - has class: ${!!c.class}`);
110
- logger.log('INFO', `[DependencyFetcher] - class type: ${c.class ? (typeof c.class === 'function' ? 'function' : typeof c.class) : 'none'}`);
111
- logger.log('INFO', `[DependencyFetcher] - class.getDependencies: ${c.class && typeof c.class.getDependencies === 'function' ? 'YES' : 'NO'}`);
112
- logger.log('INFO', `[DependencyFetcher] - has getDependencies: ${typeof c.getDependencies === 'function' ? 'YES' : 'NO'}`);
113
- logger.log('INFO', `[DependencyFetcher] - has dependencies array: ${Array.isArray(c.dependencies) ? `YES (${c.dependencies.length} items)` : 'NO'}`);
114
- if (Array.isArray(c.dependencies)) {
115
- logger.log('INFO', `[DependencyFetcher] - dependencies array: ${c.dependencies.join(', ')}`);
116
- }
117
-
118
- // [FIX] Support both .getDependencies() method and .dependencies array
119
- // CRITICAL: Prefer class.getDependencies() over manifest.dependencies
120
- // because the class method returns original case-sensitive names,
121
- // while manifest.dependencies contains normalized names
122
- let reqs = [];
123
- if (c.class && typeof c.class.getDependencies === 'function') {
124
- // Use the class method - returns original case-sensitive names
125
- reqs = c.class.getDependencies();
126
- logger.log('INFO', `[DependencyFetcher] ✅ Using c.class.getDependencies() - returned: ${JSON.stringify(reqs)}`);
127
- } else if (typeof c.getDependencies === 'function') {
128
- // Fallback: direct method call (if c is the class itself)
129
- reqs = c.getDependencies();
130
- logger.log('INFO', `[DependencyFetcher] ✅ Using c.getDependencies() - returned: ${JSON.stringify(reqs)}`);
131
- } else if (c.dependencies && Array.isArray(c.dependencies)) {
132
- // Last resort: use manifest's dependencies array (normalized)
133
- // This is less ideal because names are normalized, but we'll use them as-is
134
- reqs = c.dependencies;
135
- logger.log('INFO', `[DependencyFetcher] ⚠️ Using c.dependencies array (normalized) - returned: ${JSON.stringify(reqs)}`);
136
- } else {
137
- logger.log('WARN', `[DependencyFetcher] ❌ No way to get dependencies for ${calcName} - all methods failed`);
138
- }
139
-
140
- if (Array.isArray(reqs)) {
141
- logger.log('INFO', `[DependencyFetcher] ✅ Found ${reqs.length} dependencies for ${calcName}: ${reqs.join(', ')}`);
142
- reqs.forEach(r => {
143
- // We map the normalized version to the original requested version
144
- // This ensures we fetch the right file (normalized) but return it
145
- // with the casing the user code expects (original).
146
- needed.set(normalizeName(r), r);
147
- });
148
- } else {
149
- logger.log('WARN', `[DependencyFetcher] ⚠️ reqs is not an array for ${calcName}: ${typeof reqs}`);
150
- }
151
- });
152
-
153
- if (needed.size === 0) {
154
- logger.log('WARN', `[DependencyFetcher] ⚠️ No dependencies needed - returning empty object`);
155
- return {};
156
- }
157
-
158
- const calcNames = calcs.map(c => c.name || c.constructor?.name || 'unknown').join(', ');
159
- logger.log('INFO', `[DependencyFetcher] Fetching ${needed.size} dependencies for computation(s): ${calcNames} (date: ${dStr})`);
160
-
161
- // DEBUG: Log what dependencies we're looking for
162
- const depList = Array.from(needed.entries()).map(([norm, orig]) => `${orig} (normalized: ${norm})`).join(', ');
163
- logger.log('INFO', `[DependencyFetcher] Dependencies requested: ${depList}`);
164
-
165
- const results = {};
166
- const missingDeps = [];
167
- const emptyDeps = [];
168
-
169
- // Helper to build path string
170
- const buildPath = (category, normName) => {
171
- return `${config.resultsCollection || 'computation_results'}/${dStr}/${config.resultsSubcollection || 'results'}/${category}/${config.computationsSubcollection || 'computations'}/${normName}`;
172
- };
173
-
174
- // CHANGED: Iterate over the entries to access both normalized and original names
175
- const promises = Array.from(needed.entries()).map(async ([normName, originalName]) => {
176
- // Resolve Category from Lookup, default to 'analytics' if unknown
177
- // Note: manifestLookup keys are expected to be normalized
178
- const category = manifestLookup[normName] || 'analytics';
179
- const path = buildPath(category, normName);
180
-
181
- try {
182
- // Pass logger in config for fetchSingleResult
183
- const fetchConfig = { ...config, logger };
184
-
185
- // Fetch using the normalized name (system standard)
186
- const data = await fetchSingleResult(db, fetchConfig, dStr, normName, category);
187
-
188
- // CRITICAL: Validate that dependency exists and has data
189
- if (!data) {
190
- missingDeps.push({ name: originalName, normalizedName: normName, path });
191
- // Log level depends on context - ERROR for current date, INFO for historical
192
- if (allowMissing) {
193
- logger.log('INFO', `[DependencyFetcher] ⚠️ Missing dependency '${originalName}' (${normName}) from: ${path} (Historical context - allowed)`);
194
- } else {
195
- logger.log('ERROR', `[DependencyFetcher] ❌ Missing required dependency '${originalName}' (${normName}) from: ${path}`);
196
- }
197
- } else if (isDataEmpty(data)) {
198
- emptyDeps.push({ name: originalName, normalizedName: normName, path });
199
- // Log level depends on context - ERROR for current date, INFO for historical
200
- if (allowMissing) {
201
- logger.log('INFO', `[DependencyFetcher] ⚠️ Empty dependency '${originalName}' (${normName}) from: ${path} (Historical context - allowed)`);
202
- } else {
203
- logger.log('ERROR', `[DependencyFetcher] ❌ Empty dependency '${originalName}' (${normName}) from: ${path} - Document exists but contains no usable data`);
204
- }
205
- } else {
206
- // CHANGED: Store result using the ORIGINAL name so context.computed['CaseSensitive'] works
207
- results[originalName] = data;
208
- // DEBUG: Log successful dependency load
209
- const dataKeys = Object.keys(data);
210
- logger.log('INFO', `[DependencyFetcher] ✅ Stored dependency '${originalName}' in results. Keys: ${dataKeys.length} (sample: ${dataKeys.slice(0, 5).join(', ')})`);
211
- }
212
- } catch (e) {
213
- missingDeps.push({ name: originalName, normalizedName: normName, path, error: e.message });
214
- // Log level depends on context - ERROR for current date, INFO for historical
215
- if (allowMissing) {
216
- logger.log('INFO', `[DependencyFetcher] ⚠️ Failed to load dependency '${originalName}' (${normName}) from: ${path} - Error: ${e.message} (Historical context - allowed)`);
217
- } else {
218
- logger.log('ERROR', `[DependencyFetcher] ❌ Failed to load dependency '${originalName}' (${normName}) from: ${path} - Error: ${e.message}`);
219
- }
220
- }
221
- });
222
-
223
- await Promise.all(promises);
224
-
225
- // DEBUG: Log what we're returning
226
- const resultKeys = Object.keys(results);
227
- logger.log('INFO', `[DependencyFetcher] ✅ Returning ${resultKeys.length} dependencies: ${resultKeys.join(', ')}`);
228
-
229
- // CRITICAL: Fail if any required dependencies are missing or empty
230
- // EXCEPTION: For historical/lookback scenarios, missing dependencies are permissible
231
- if ((missingDeps.length > 0 || emptyDeps.length > 0) && !allowMissing) {
232
- const missingList = missingDeps.map(d => `'${d.name}' (path: ${d.path}${d.error ? `, error: ${d.error}` : ''})`).join(', ');
233
- const emptyList = emptyDeps.map(d => `'${d.name}' (path: ${d.path})`).join(', ');
234
-
235
- const errorMsg = `[DependencyFetcher] ❌ CRITICAL: Cannot proceed - Required dependencies missing or empty for computation(s): ${calcNames}\n` +
236
- `Missing dependencies (${missingDeps.length}): ${missingList}\n` +
237
- (emptyDeps.length > 0 ? `Empty dependencies (${emptyDeps.length}): ${emptyList}\n` : '') +
238
- `Date: ${dStr}\n` +
239
- `This computation will FAIL and no results will be saved.`;
240
-
241
- logger.log('ERROR', errorMsg);
242
- throw new Error(errorMsg);
243
- } else if (missingDeps.length > 0 || emptyDeps.length > 0) {
244
- // Historical/lookback context - log but allow missing dependencies
245
- const missingList = missingDeps.map(d => `'${d.name}' (path: ${d.path})`).join(', ');
246
- const emptyList = emptyDeps.map(d => `'${d.name}' (path: ${d.path})`).join(', ');
247
- logger.log('INFO', `[DependencyFetcher] ⚠️ Historical/Lookback context: Missing/empty dependencies allowed for ${calcNames} on ${dStr}. Missing: ${missingList}${emptyDeps.length > 0 ? `, Empty: ${emptyList}` : ''}`);
248
- }
249
-
250
- return results;
251
- }
252
-
253
- /**
254
- * Fetches result series (Historical data) for lookbacks.
255
- * @param {string} endDateStr - The most recent date.
256
- * @param {Array} calcNames - Names of computations to fetch.
257
- * @param {Object} manifestLookup - Map of { [calcName]: categoryString }.
258
- */
259
- async function fetchResultSeries(endDateStr, calcNames, manifestLookup, config, deps, lookbackDays) {
260
- const { db, logger } = deps;
261
- const results = {};
262
- const dates = [];
263
-
264
- // Generate date list (starting from yesterday relative to endDateStr)
265
- const d = new Date(endDateStr);
266
- for (let i = 0; i < lookbackDays; i++) {
267
- d.setUTCDate(d.getUTCDate() - 1);
268
- dates.push(d.toISOString().slice(0, 10));
269
- }
270
-
271
- // Initialize structure
272
- calcNames.forEach(name => { results[normalizeName(name)] = {}; });
273
-
274
- logger.log('INFO', `[DependencyFetcher] Loading series for ${calcNames.length} computation dependencies over ${lookbackDays} days: ${calcNames.join(', ')}`);
275
-
276
- const fetchOps = [];
277
-
278
- for (const dateStr of dates) {
279
- for (const rawName of calcNames) {
280
- const normName = normalizeName(rawName);
281
- const category = manifestLookup[normName] || 'analytics';
282
-
283
- fetchOps.push(async () => {
284
- const fetchConfig = { ...config, logger };
285
- const val = await fetchSingleResult(db, fetchConfig, dateStr, rawName, category);
286
- // CRITICAL: For series/lookback, we allow missing dates (historical lookback may have gaps)
287
- // This is expected behavior - not all historical dates will have data
288
- // But we still validate that the data isn't empty if it exists
289
- if (val && !isDataEmpty(val)) {
290
- if (!results[normName]) results[normName] = {};
291
- results[normName][dateStr] = val;
292
- } else if (val && isDataEmpty(val)) {
293
- // Log but don't fail - series can have gaps, empty data is treated as missing
294
- logger.log('INFO', `[DependencyFetcher] ⚠️ Empty dependency '${rawName}' found at ${dateStr} in series (allowing gap - historical lookback)`);
295
- }
296
- // If val is null, that's fine - missing dates in historical series are permissible
297
- });
298
- }
299
- }
300
-
301
- // Limited concurrency batch execution (Batch size 20)
302
- const BATCH_SIZE = 20;
303
- for (let i = 0; i < fetchOps.length; i += BATCH_SIZE) {
304
- await Promise.all(fetchOps.slice(i, i + BATCH_SIZE).map(fn => fn()));
305
- }
306
-
307
- return results;
308
- }
309
-
310
- /**
311
- * Core Helper: Fetches a single result, handles Sharding & Compression.
312
- */
313
- async function fetchSingleResult(db, config, dateStr, name, category) {
314
- const resultsCollection = config.resultsCollection || 'computation_results';
315
- const resultsSubcollection = config.resultsSubcollection || 'results';
316
- const computationsSubcollection = config.computationsSubcollection || 'computations';
317
-
318
- const path = `${resultsCollection}/${dateStr}/${resultsSubcollection}/${category}/${computationsSubcollection}/${name}`;
319
-
320
- // Log path - use console.log if logger not available (for backward compatibility)
321
- if (config.logger) {
322
- config.logger.log('INFO', `[DependencyFetcher] 📂 Loading Dependency '${name}' from: ${path}`);
323
- } else {
324
- console.log(`[DependencyFetcher] 📂 Loading Dependency '${name}' from: ${path}`);
325
- }
326
-
327
- const docRef = db.collection(resultsCollection)
328
- .doc(dateStr)
329
- .collection(resultsSubcollection)
330
- .doc(category)
331
- .collection(computationsSubcollection)
332
- .doc(name);
333
-
334
- const snap = await docRef.get();
335
- if (!snap.exists) {
336
- // Log the missing document path clearly
337
- if (config.logger) {
338
- config.logger.log('ERROR', `[DependencyFetcher] ❌ Document does not exist at: ${path}`);
339
- } else {
340
- console.error(`[DependencyFetcher] ❌ Document does not exist at: ${path}`);
341
- }
342
- return null;
343
- }
344
-
345
- let data = snap.data();
346
-
347
- // CRITICAL: Don't check if empty yet - we need to load shards/compressed data first
348
- // A sharded document will only have metadata in the pointer doc, but the actual data is in shards
349
- // A compressed document will only have metadata + payload, but the actual data is in the payload
350
-
351
- // 1. Handle Compression
352
- if (data._compressed && data.payload) {
353
- try {
354
- const buffer = (data.payload instanceof Buffer) ? data.payload : data.payload.toDate();
355
- const decompressed = zlib.gunzipSync(buffer);
356
- const jsonStr = decompressed.toString('utf8');
357
- const realData = JSON.parse(jsonStr);
358
- // Merge decompressed data
359
- data = { ...data, ...realData };
360
- delete data.payload;
361
- } catch (e) {
362
- const errorMsg = `Decompression failed for ${name}: ${e.message}`;
363
- if (config.logger) {
364
- config.logger.log('ERROR', `[DependencyFetcher] ❌ ${errorMsg} at: ${path}`);
365
- } else {
366
- console.error(`[DependencyFetcher] ❌ ${errorMsg} at: ${path}`);
367
- }
368
- return null;
369
- }
370
- }
371
-
372
- // 2. Handle Sharding (MUST happen before empty check)
373
- if (data._sharded) {
374
- const shardPath = `${path}/_shards`;
375
- if (config.logger) {
376
- config.logger.log('INFO', `[DependencyFetcher] 📂 Loading Shards for '${name}' from: ${shardPath}`);
377
- } else {
378
- console.log(`[DependencyFetcher] 📂 Loading Shards for '${name}' from: ${shardPath}`);
379
- }
380
-
381
- const shardCol = docRef.collection('_shards');
382
- const shardSnaps = await shardCol.get();
383
-
384
- if (shardSnaps.empty) {
385
- // No shards found - this is a problem
386
- if (config.logger) {
387
- config.logger.log('ERROR', `[DependencyFetcher] ❌ Document marked as sharded but no shards found at: ${shardPath}`);
388
- } else {
389
- console.error(`[DependencyFetcher] ❌ Document marked as sharded but no shards found at: ${shardPath}`);
390
- }
391
- return null; // Return null so it gets caught as missing
392
- }
393
-
394
- // Merge shard contents
395
- let hasData = false;
396
- shardSnaps.forEach(shard => {
397
- let shardData = shard.data();
398
- const shardId = shard.id;
399
- if (config.logger) {
400
- config.logger.log('TRACE', `[DependencyFetcher] 📂 Loading Shard '${shardId}' for '${name}' from: ${shardPath}/${shardId}`);
401
- }
402
-
403
- // CRITICAL: Shards themselves can be compressed (common in big data)
404
- // Decompress the shard if needed before merging
405
- if (shardData._compressed && shardData.payload) {
406
- try {
407
- const buffer = (shardData.payload instanceof Buffer) ? shardData.payload :
408
- (shardData.payload._byteString ? Buffer.from(shardData.payload._byteString, 'base64') :
409
- Buffer.from(shardData.payload));
410
- const decompressed = zlib.gunzipSync(buffer);
411
- const jsonStr = decompressed.toString('utf8');
412
- const realData = JSON.parse(jsonStr);
413
- // If it's double-encoded, parse again
414
- const parsedData = (typeof realData === 'string') ? JSON.parse(realData) : realData;
415
- shardData = { ...shardData, ...parsedData };
416
- delete shardData.payload;
417
- } catch (e) {
418
- if (config.logger) {
419
- config.logger.log('ERROR', `[DependencyFetcher] ❌ Failed to decompress shard '${shardId}' for '${name}': ${e.message}`);
420
- } else {
421
- console.error(`[DependencyFetcher] ❌ Failed to decompress shard '${shardId}' for '${name}': ${e.message}`);
422
- }
423
- // Continue with uncompressed data if decompression fails
424
- }
425
- }
426
-
427
- // Merge shard contents, ignoring internal metadata if it clashes
428
- Object.entries(shardData).forEach(([k, v]) => {
429
- if (!k.startsWith('_')) {
430
- data[k] = v;
431
- hasData = true;
432
- }
433
- });
434
- });
435
-
436
- // If shards contained no actual data, treat as empty
437
- if (!hasData) {
438
- if (config.logger) {
439
- config.logger.log('ERROR', `[DependencyFetcher] ❌ Shards found but contain no data at: ${shardPath}`);
440
- } else {
441
- console.error(`[DependencyFetcher] ❌ Shards found but contain no data at: ${shardPath}`);
442
- }
443
- return null;
444
- }
445
-
446
- // After loading shards, remove shard metadata from data object for cleaner output
447
- // Keep only the actual data fields
448
- const cleanedData = {};
449
- const dataKeys = [];
450
- Object.entries(data).forEach(([k, v]) => {
451
- if (!k.startsWith('_')) {
452
- cleanedData[k] = v;
453
- dataKeys.push(k);
454
- }
455
- });
456
- data = cleanedData;
457
-
458
- // Log what we loaded for debugging
459
- if (config.logger) {
460
- config.logger.log('INFO', `[DependencyFetcher] ✅ Loaded ${shardSnaps.size} shard(s) for '${name}'. Data fields: ${dataKeys.length > 0 ? dataKeys.slice(0, 10).join(', ') + (dataKeys.length > 10 ? `... (+${dataKeys.length - 10} more)` : '') : 'none'}`);
461
- }
462
- }
463
-
464
- // Final validation: ensure we have usable data after all processing (decompression + sharding)
465
- // Only check if we haven't already determined it's empty
466
- if (isDataEmpty(data)) {
467
- if (config.logger) {
468
- config.logger.log('ERROR', `[DependencyFetcher] ❌ Dependency '${name}' loaded but is empty (no usable data) at: ${path}`);
469
- } else {
470
- console.error(`[DependencyFetcher] ❌ Dependency '${name}' loaded but is empty (no usable data) at: ${path}`);
471
- }
472
- return null;
473
- }
474
-
475
- return data;
476
- }
477
-
478
- module.exports = { fetchDependencies, fetchResultSeries, fetchExistingResults };