bulltrackers-module 1.0.733 → 1.0.734
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.
- package/functions/computation-system-v2/README.md +152 -0
- package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +720 -0
- package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +176 -0
- package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +294 -0
- package/functions/computation-system-v2/computations/TestComputation.js +46 -0
- package/functions/computation-system-v2/computations/UserPortfolioSummary.js +172 -0
- package/functions/computation-system-v2/config/bulltrackers.config.js +317 -0
- package/functions/computation-system-v2/framework/core/Computation.js +73 -0
- package/functions/computation-system-v2/framework/core/Manifest.js +223 -0
- package/functions/computation-system-v2/framework/core/RuleInjector.js +53 -0
- package/functions/computation-system-v2/framework/core/Rules.js +231 -0
- package/functions/computation-system-v2/framework/core/RunAnalyzer.js +163 -0
- package/functions/computation-system-v2/framework/cost/CostTracker.js +154 -0
- package/functions/computation-system-v2/framework/data/DataFetcher.js +399 -0
- package/functions/computation-system-v2/framework/data/QueryBuilder.js +232 -0
- package/functions/computation-system-v2/framework/data/SchemaRegistry.js +287 -0
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +498 -0
- package/functions/computation-system-v2/framework/execution/TaskRunner.js +35 -0
- package/functions/computation-system-v2/framework/execution/middleware/CostTrackerMiddleware.js +32 -0
- package/functions/computation-system-v2/framework/execution/middleware/LineageMiddleware.js +32 -0
- package/functions/computation-system-v2/framework/execution/middleware/Middleware.js +14 -0
- package/functions/computation-system-v2/framework/execution/middleware/ProfilerMiddleware.js +47 -0
- package/functions/computation-system-v2/framework/index.js +45 -0
- package/functions/computation-system-v2/framework/lineage/LineageTracker.js +147 -0
- package/functions/computation-system-v2/framework/monitoring/Profiler.js +80 -0
- package/functions/computation-system-v2/framework/resilience/Checkpointer.js +66 -0
- package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +327 -0
- package/functions/computation-system-v2/framework/storage/StateRepository.js +286 -0
- package/functions/computation-system-v2/framework/storage/StorageManager.js +469 -0
- package/functions/computation-system-v2/framework/storage/index.js +9 -0
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +86 -0
- package/functions/computation-system-v2/framework/utils/Graph.js +205 -0
- package/functions/computation-system-v2/handlers/dispatcher.js +109 -0
- package/functions/computation-system-v2/handlers/index.js +23 -0
- package/functions/computation-system-v2/handlers/onDemand.js +289 -0
- package/functions/computation-system-v2/handlers/scheduler.js +327 -0
- package/functions/computation-system-v2/index.js +163 -0
- package/functions/computation-system-v2/rules/index.js +49 -0
- package/functions/computation-system-v2/rules/instruments.js +465 -0
- package/functions/computation-system-v2/rules/metrics.js +304 -0
- package/functions/computation-system-v2/rules/portfolio.js +534 -0
- package/functions/computation-system-v2/rules/rankings.js +655 -0
- package/functions/computation-system-v2/rules/social.js +562 -0
- package/functions/computation-system-v2/rules/trades.js +545 -0
- package/functions/computation-system-v2/scripts/migrate-sectors.js +73 -0
- package/functions/computation-system-v2/test/test-dispatcher.js +317 -0
- package/functions/computation-system-v2/test/test-framework.js +500 -0
- package/functions/computation-system-v2/test/test-real-execution.js +166 -0
- package/functions/computation-system-v2/test/test-real-integration.js +194 -0
- package/functions/computation-system-v2/test/test-refactor-e2e.js +131 -0
- package/functions/computation-system-v2/test/test-results.json +31 -0
- package/functions/computation-system-v2/test/test-risk-metrics-computation.js +329 -0
- package/functions/computation-system-v2/test/test-scheduler.js +204 -0
- package/functions/computation-system-v2/test/test-storage.js +449 -0
- package/functions/orchestrator/index.js +18 -26
- package/package.json +3 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Schema Registry - Dynamic schema discovery with caching
|
|
3
|
+
*
|
|
4
|
+
* Core innovation of v2: No hardcoded schemas. Instead, we:
|
|
5
|
+
* 1. Fetch schemas from BigQuery INFORMATION_SCHEMA on first access
|
|
6
|
+
* 2. Cache them with configurable TTL
|
|
7
|
+
* 3. Validate all queries against cached schemas BEFORE sending to BigQuery
|
|
8
|
+
*
|
|
9
|
+
* This prevents runtime query failures and eliminates schema maintenance burden.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { BigQuery } = require('@google-cloud/bigquery');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} ColumnInfo
|
|
16
|
+
* @property {string} name - Column name
|
|
17
|
+
* @property {string} type - BigQuery data type (STRING, INT64, DATE, JSON, etc.)
|
|
18
|
+
* @property {boolean} nullable - Whether column allows NULL
|
|
19
|
+
* @property {number} position - Ordinal position in table
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} TableSchema
|
|
24
|
+
* @property {string} tableName - Table name
|
|
25
|
+
* @property {ColumnInfo[]} columns - Array of column definitions
|
|
26
|
+
* @property {Set<string>} columnNames - Set of column names for fast lookup
|
|
27
|
+
* @property {number} fetchedAt - Timestamp when schema was fetched
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
class SchemaRegistry {
|
|
31
|
+
/**
|
|
32
|
+
* @param {Object} config - Configuration object
|
|
33
|
+
* @param {string} config.projectId - GCP project ID
|
|
34
|
+
* @param {string} config.dataset - BigQuery dataset name
|
|
35
|
+
* @param {string} [config.location='US'] - BigQuery location
|
|
36
|
+
* @param {number} [config.cacheTTLMs=3600000] - Schema cache TTL in milliseconds (default: 1 hour)
|
|
37
|
+
* @param {Object} [logger] - Logger instance
|
|
38
|
+
*/
|
|
39
|
+
constructor(config, logger = null) {
|
|
40
|
+
this.projectId = config.projectId;
|
|
41
|
+
this.dataset = config.dataset;
|
|
42
|
+
this.location = config.location || 'US';
|
|
43
|
+
this.cacheTTLMs = config.cacheTTLMs || 3600000; // 1 hour default
|
|
44
|
+
this.logger = logger;
|
|
45
|
+
|
|
46
|
+
this.client = new BigQuery({ projectId: this.projectId });
|
|
47
|
+
this.cache = new Map();
|
|
48
|
+
|
|
49
|
+
// Track schema fetch stats for monitoring
|
|
50
|
+
this.stats = {
|
|
51
|
+
hits: 0,
|
|
52
|
+
misses: 0,
|
|
53
|
+
refreshes: 0,
|
|
54
|
+
errors: 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get schema for a table, fetching from BigQuery if not cached.
|
|
60
|
+
* @param {string} tableName - Table name (without dataset prefix)
|
|
61
|
+
* @returns {Promise<TableSchema>}
|
|
62
|
+
*/
|
|
63
|
+
async getSchema(tableName) {
|
|
64
|
+
const cached = this.cache.get(tableName);
|
|
65
|
+
|
|
66
|
+
if (cached && !this._isExpired(cached)) {
|
|
67
|
+
this.stats.hits++;
|
|
68
|
+
return cached;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (cached) {
|
|
72
|
+
this.stats.refreshes++;
|
|
73
|
+
this._log('DEBUG', `Schema cache expired for ${tableName}, refreshing...`);
|
|
74
|
+
} else {
|
|
75
|
+
this.stats.misses++;
|
|
76
|
+
this._log('DEBUG', `Schema cache miss for ${tableName}, fetching...`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return await this._fetchAndCacheSchema(tableName);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if a table exists in the dataset.
|
|
84
|
+
* @param {string} tableName - Table name
|
|
85
|
+
* @returns {Promise<boolean>}
|
|
86
|
+
*/
|
|
87
|
+
async tableExists(tableName) {
|
|
88
|
+
try {
|
|
89
|
+
await this.getSchema(tableName);
|
|
90
|
+
return true;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
if (e.message.includes('not found') || e.message.includes('Not found')) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
throw e;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validate that columns exist in a table's schema.
|
|
101
|
+
* Throws if any column doesn't exist.
|
|
102
|
+
* @param {string} tableName - Table name
|
|
103
|
+
* @param {string[]} columns - Column names to validate
|
|
104
|
+
* @throws {Error} If any column doesn't exist
|
|
105
|
+
*/
|
|
106
|
+
async validateColumns(tableName, columns) {
|
|
107
|
+
const schema = await this.getSchema(tableName);
|
|
108
|
+
const invalid = columns.filter(col => !schema.columnNames.has(col));
|
|
109
|
+
|
|
110
|
+
if (invalid.length > 0) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`[SchemaRegistry] Invalid columns for table '${tableName}': ${invalid.join(', ')}. ` +
|
|
113
|
+
`Valid columns: ${Array.from(schema.columnNames).join(', ')}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Validate a WHERE clause object against table schema.
|
|
120
|
+
* @param {string} tableName - Table name
|
|
121
|
+
* @param {Object} whereClause - Object of { columnName: value } conditions
|
|
122
|
+
* @throws {Error} If any column in the where clause doesn't exist
|
|
123
|
+
*/
|
|
124
|
+
async validateWhere(tableName, whereClause) {
|
|
125
|
+
if (!whereClause || typeof whereClause !== 'object') return;
|
|
126
|
+
await this.validateColumns(tableName, Object.keys(whereClause));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get column type for a specific column.
|
|
131
|
+
* @param {string} tableName - Table name
|
|
132
|
+
* @param {string} columnName - Column name
|
|
133
|
+
* @returns {Promise<string|null>} Column type or null if not found
|
|
134
|
+
*/
|
|
135
|
+
async getColumnType(tableName, columnName) {
|
|
136
|
+
const schema = await this.getSchema(tableName);
|
|
137
|
+
const column = schema.columns.find(c => c.name === columnName);
|
|
138
|
+
return column ? column.type : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if a table has a specific column.
|
|
143
|
+
* @param {string} tableName - Table name
|
|
144
|
+
* @param {string} columnName - Column name
|
|
145
|
+
* @returns {Promise<boolean>}
|
|
146
|
+
*/
|
|
147
|
+
async hasColumn(tableName, columnName) {
|
|
148
|
+
const schema = await this.getSchema(tableName);
|
|
149
|
+
return schema.columnNames.has(columnName);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Pre-warm the cache by fetching schemas for specified tables.
|
|
154
|
+
* Useful at startup to avoid cold-start latency.
|
|
155
|
+
* @param {string[]} tableNames - Tables to pre-warm
|
|
156
|
+
* @returns {Promise<Object>} Summary of pre-warm results
|
|
157
|
+
*/
|
|
158
|
+
async warmCache(tableNames) {
|
|
159
|
+
const results = { success: [], failed: [] };
|
|
160
|
+
|
|
161
|
+
await Promise.all(tableNames.map(async (tableName) => {
|
|
162
|
+
try {
|
|
163
|
+
await this.getSchema(tableName);
|
|
164
|
+
results.success.push(tableName);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
results.failed.push({ table: tableName, error: e.message });
|
|
167
|
+
}
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
this._log('INFO', `Schema cache warmed: ${results.success.length} success, ${results.failed.length} failed`);
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Clear the schema cache (useful for testing or after schema changes).
|
|
176
|
+
* @param {string} [tableName] - Specific table to clear, or all if not specified
|
|
177
|
+
*/
|
|
178
|
+
clearCache(tableName = null) {
|
|
179
|
+
if (tableName) {
|
|
180
|
+
this.cache.delete(tableName);
|
|
181
|
+
this._log('DEBUG', `Cleared schema cache for ${tableName}`);
|
|
182
|
+
} else {
|
|
183
|
+
this.cache.clear();
|
|
184
|
+
this._log('DEBUG', 'Cleared entire schema cache');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get cache statistics.
|
|
190
|
+
* @returns {Object} Cache statistics
|
|
191
|
+
*/
|
|
192
|
+
getStats() {
|
|
193
|
+
return {
|
|
194
|
+
...this.stats,
|
|
195
|
+
cachedTables: this.cache.size,
|
|
196
|
+
cacheContents: Array.from(this.cache.keys())
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* List all tables in the dataset.
|
|
202
|
+
* @returns {Promise<string[]>} Array of table names
|
|
203
|
+
*/
|
|
204
|
+
async listTables() {
|
|
205
|
+
const query = `
|
|
206
|
+
SELECT table_name
|
|
207
|
+
FROM \`${this.projectId}.${this.dataset}.INFORMATION_SCHEMA.TABLES\`
|
|
208
|
+
WHERE table_type = 'BASE TABLE'
|
|
209
|
+
ORDER BY table_name
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const [rows] = await this.client.query({ query, location: this.location });
|
|
214
|
+
return rows.map(r => r.table_name);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
this._log('ERROR', `Failed to list tables: ${e.message}`);
|
|
217
|
+
throw e;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// =========================================================================
|
|
222
|
+
// PRIVATE METHODS
|
|
223
|
+
// =========================================================================
|
|
224
|
+
|
|
225
|
+
async _fetchAndCacheSchema(tableName) {
|
|
226
|
+
const query = `
|
|
227
|
+
SELECT
|
|
228
|
+
column_name,
|
|
229
|
+
data_type,
|
|
230
|
+
is_nullable,
|
|
231
|
+
ordinal_position
|
|
232
|
+
FROM \`${this.projectId}.${this.dataset}.INFORMATION_SCHEMA.COLUMNS\`
|
|
233
|
+
WHERE table_name = @tableName
|
|
234
|
+
ORDER BY ordinal_position
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const [rows] = await this.client.query({
|
|
239
|
+
query,
|
|
240
|
+
params: { tableName },
|
|
241
|
+
location: this.location
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (rows.length === 0) {
|
|
245
|
+
throw new Error(`Table '${tableName}' not found in dataset '${this.dataset}'`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const columns = rows.map(r => ({
|
|
249
|
+
name: r.column_name,
|
|
250
|
+
type: r.data_type,
|
|
251
|
+
nullable: r.is_nullable === 'YES',
|
|
252
|
+
position: r.ordinal_position
|
|
253
|
+
}));
|
|
254
|
+
|
|
255
|
+
const schema = {
|
|
256
|
+
tableName,
|
|
257
|
+
columns,
|
|
258
|
+
columnNames: new Set(columns.map(c => c.name)),
|
|
259
|
+
fetchedAt: Date.now()
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
this.cache.set(tableName, schema);
|
|
263
|
+
this._log('INFO', `Cached schema for '${tableName}': ${columns.length} columns`);
|
|
264
|
+
|
|
265
|
+
return schema;
|
|
266
|
+
|
|
267
|
+
} catch (e) {
|
|
268
|
+
this.stats.errors++;
|
|
269
|
+
this._log('ERROR', `Failed to fetch schema for '${tableName}': ${e.message}`);
|
|
270
|
+
throw e;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_isExpired(schema) {
|
|
275
|
+
return Date.now() - schema.fetchedAt > this.cacheTTLMs;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
_log(level, message) {
|
|
279
|
+
if (this.logger && typeof this.logger.log === 'function') {
|
|
280
|
+
this.logger.log(level, `[SchemaRegistry] ${message}`);
|
|
281
|
+
} else if (level === 'ERROR') {
|
|
282
|
+
console.error(`[SchemaRegistry] ${message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = { SchemaRegistry };
|